<!DOCTYPE html>
<html>
<!--
Reference:
  https://github.com/jussi-kalliokoski/audiolib.js/wiki/Tutorials%3A-Synthesizing-a-Song
  http://freespace.virgin.net/hugo.elias/models/m_perlin.htm
-->
<head>
  <title>AudioLib Test</title>
  
        <!-- debugging helpers -->
		<script src="audiolib.min.js" type="text/javascript"></script>
		<script src="SimplexNoise.js" type="text/javascript"></script>
		<script src="NoiseConverter.js" type="text/javascript"></script>
        <script type="text/javascript">
                // do not use
                function sleep(delay) {
                        var start = new Date().getTime();
                        while (new Date().getTime() < start + delay);
                }
                // use!
                function echo(line, string) {
                        document.getElementById("debugEcho"+line).textContent = string;
                }
        </script>
        <script src="audiolib.min.js" type="text/javascript"></script>

        <!-- function generator -->
        <script type="text/javascript">        
                function sineFunctionGenerator() {}
                sineFunctionGenerator.prototype = {
                        "tick" : 0,
                        "gain" : 100000,
                        "increment" : 10,
                        
                        //--------------------------------------------------------------------------------------------
                        // public methods
                        //--------------------------------------------------------------------------------------------
                        "getValue" : function() {
                                var value = this.gain * Math.sin(this.tick * (Math.PI/180));
                                this.tick += this.increment;
                                return value;
                        }
                }
                var sineGenerator = new sineFunctionGenerator();
        </script>
        
        <!-- noise generators -->
        <script type="text/javascript">
                /*
                 * whiteNoise
                 */
                function whiteNoiseGenerator() { }
                whiteNoiseGenerator.prototype = {
                        "min" : -1000000.0,
                        "max" : 1000000.0,
                        
                        //--------------------------------------------------------------------------------------------
                        // public methods
                        //--------------------------------------------------------------------------------------------
                        "getNoise" : function(input) {
                                return Math.floor(Math.random() * (this.max - this.min + 1)) + this.min;
                        }
                }
                var whiteNoise = new whiteNoiseGenerator();
        
                /*
                 * perlinNoise1D
                 */
                function perlinNoise1DGenerator() {}
                perlinNoise1DGenerator.prototype = {
                        "persistence" : .2,
                        "primes" : 0,
                        "octaves" : 3,
                        "primeset" : [ // element number must match octaves
                                {
                                        p1 : 15731,
                                        p2 : 789221,
                                        p3 : 1376312589,
                                        p4 : 1073741824.0
                                },
                                {
                                        p1 : 8219,
                                        p2 : 692051,
                                        p3 : 2009807,
                                        p4 : 19468639.0
                                },
                                {
                                        p1 : 173177,
                                        p2 : 39770261,
                                        p3 : 1000984351,
                                        p4 : 12917269.0
                                }
                        ],
                        "noise" : function(x) {
                                x = Math.pow(x<<13, x);
                                return ( 1.0 - ( (x * (x * x * this.primeset[this.primes].p1 + this.primeset[this.primes].p2) + this.primeset[this.primes].p3) & 0x7fffffff) / this.primeset[this.primes].p4);
                        },
                        "cosineInterpolate" : function(a, b, x) {
                                var ft = x * Math.PI;
                                var f = (1 - Math.cos(ft)) * .5;
                                return  a*(1-f) + b*f;
                        },
                        "smoothedNoise" : function(x) {
                                return this.noise(x)/2  +  this.noise(x-1)/4  +  this.noise(x+1)/4;
                        },
                        "interpolatedNoise" : function(x) {
                                var integer_X    = Math.floor(x);
                                var fractional_X = x - integer_X;
                                var v1 = this.smoothedNoise(integer_X);
                                var v2 = this.smoothedNoise(integer_X + 1);
                                return this.cosineInterpolate(v1 , v2 , fractional_X);
                        },
                        
                        //--------------------------------------------------------------------------------------------
                        // public methods
                        //--------------------------------------------------------------------------------------------
                        "getNoise" : function(input) {
                                var total = 0;
                                var p = this.persistence;
                                var n = this.octaves;
                                var i, frequency, amplitude;
                                var oSimplexNoise = new SimplexNoise(5);
                                for (i = 0; i < n; i++) {
                                        frequency = Math.pow(2, i);
                                        amplitude = Math.pow(p, i);
                                        this.primes = i;
                                        total = total + noiseR(input * frequency,input * frequency) * amplitude;
                                }
                                echo (1, "Perlin:  Input="+input+", Output="+total);
                                echo (2, "Perlin:  freq="+frequency+", amp="+amplitude+", primes="+this.primes+", total="+total);
                                return total;
                        }
                }
                var perlinNoise1D = new perlinNoise1DGenerator();
        
                /*
                 * generates a constant for testing only
                 */
                function constantNoiseGenerator() {}
                constantNoiseGenerator.prototype = {

                        //--------------------------------------------------------------------------------------------
                        // public methods
                        //--------------------------------------------------------------------------------------------
                        "getNoise" : function(input) {
                                return 111234.5;
                        }
                }
                var constantNoise= new constantNoiseGenerator();
        </script>
        
        <!-- our audioNoiseInterface code -->
        <script type="text/javascript">
                /*
                 * audioNoiseInterface
                 * The interface from the audioLib callbacks to the audioNoise system
                 */
                function audioNoiseInterface(tempo, notesPerBeat) {
                        this.tempo = tempo;
                        this.notesPerBeat = notesPerBeat;
                }
                audioNoiseInterface.prototype = {
                
                        "tempo" : 120,
                        "notesPerBeat" : 4,
                
                        "noteRange" : 100,                                      // C0-D#8
                        "frequencyLowCutOff" : 133.0,                   // cant hear anything below this
                        "frequencyHighCutOff" : 4979.0,                // top limit
                
                        "noiseGenerator" : whiteNoise,                   // whiteNoise or perlinNoise1D or constantNoise
                        "functionGenerator" : sineGenerator,         // source data generator
                
                        /* 
                         * noiseToFrequency
                         * Converts the given noise to a midi index on the scale (noteRange), then to a musical note frequency in that range.
                         */
                        "noiseToFrequency" : function (noise) {
                                return 6.875 * Math.pow(2, (3 + ((Math.abs(noise) % this.noteRange) + 12)) / 12)*25;
                        },
						/* 
                         * noiseToMidi
                         * Converts the given noise to a midi index on the scale (noteRange), then to a musical note frequency in that range.
                         */
						"noiseToMidi" : function (noise) {
							//if Math.round
							//return Math.round(noise*127);
							return Math.round(noise*127-9);
						},
						"midiToFrequency" : function (midi) {
								//document.getElementById("theMIDI").textContent = noise*127-9+"";
                                return (440 / 32) * Math.pow(2,(midi)/12);
                        },
                
                        /*
                         * getNextFrequency
                         * Retrieves the next musical frequency
                         *        1) Get the next source function value
                         *        2) "Noise up" the function value
                         *        3) Convert the noised value to a musical note frequency
                         */
                        "getNextFrequency" : function () {
                                var frequency = 0;
                                var noise = 0;
								var midi = 0;
                                var value = this.functionGenerator.getValue();
                                
                                // get the noise for the function value and convert to frequency.
                                var i = 0, maxTries = 20; // guard against a bad function generator with no variation that results in inaudible sound
                                while ((frequency < this.frequencyLowCutOff || frequency > this.frequencyHighCutOff) && i < maxTries) {
                                        noise = this.noiseGenerator.getNoise(value);
										midi = this.noiseToMidi(noise);
                                        frequency = this.midiToFrequency(midi);
                                        i++;
                                } // while

                                // Update output
                                document.getElementById("theValue").textContent = frequency+"";
                                document.getElementById("theNoise").textContent = noise+"";
								//document.getElementByID("theMIDI").textContent = midi+"";
                                document.getElementById("theFreq").textContent = midi+"";

                                return frequency;
                        },

                        //--------------------------------------------------------------------------------------------
                        // public methods
                        //--------------------------------------------------------------------------------------------
                
                        /*
                         * setNoiseGenerator
                         * Dynamically sets the noiseGenerator
                         */
                        "setNoiseGenerator" : function(noiseOption) {
                                switch (noiseOption) {
                                        case 1:
                                                this.noiseGenerator = whiteNoise;
                                                break;
                                        case 2:
                                                this.noiseGenerator = perlinNoise1D;
                                                break;
                                        default: 
                                                this.noiseGenerator = constantNoise;
                                                break;
                                } // switch
                        },
                        
                        /*
                         * setFunctionGenerator
                         * Dynamically sets the functionGenerator
                         */
                        "setFunctionGenerator" : function(functionOption) {
                                // nothing yet
                        },
                        
                        /*
                         * getNextNote
                         * Gets the next note to play after the previous has expired.
                         * Returns a note object with "freq" and "dur" properties.
                         */
                        "getNextNote" : function () {
                                return { freq : this.getNextFrequency(), dur :  1/2 };
                        },
        
                        "getNotesPerBeat" : function () {
                                return this.notesPerBeat;
                        },
        
                        "getTempo" : function () {
                                return this.tempo;
                        }
                }
        
                // the global instance to the audioNoiseInterface
                // 120 bpm, 4 npb
                var audioNoise = new audioNoiseInterface(120, 4);
        </script>
  
        <!-- audioLib code -->
        <script type="text/javascript">
                var   leadNoteLength = 0,
                        fade = 0,
                        fadePoint = 0,
                        dev, sampleRate, lead;
        
                // called from audioCallback
                // inputs
                // gets the next song note
                // gets the song notes per beat and tempo
                // outputs
                // sets outgoing lead note freq and length from song note and song
                // sets fadepoint for this lead note
                // for a chord, we create an array of lead notes (not implemented).
                function loadNote () {
                        var note = audioNoise.getNextNote();
                
                        // Reset oscillator
                        lead.frequency = 0;
                        lead.reset();
        
                        // Set oscillator frequency
                        lead.frequency = note.freq;
                                
                        // Calculate note length in samples
                        leadNoteLength = Math.floor(note.dur * sampleRate * 60 * 
                                audioNoise.getNotesPerBeat() / audioNoise.getTempo()
                                );
        
                        // reset fade
                        fade = 0;
                
                        // define fade point
                        fadePoint = leadNoteLength - 300;
                }

                // audioLib calls this when the audio buffer needs filling
                // plays a lead note
                function audioCallback (buffer, channelCount){
                        var l = buffer.length,
                                sample, note, n, current;
        
                        // loop through each sample in the buffer
                        for (current=0; current<l; current+= channelCount){
                
                                if (leadNoteLength == 0) loadNote();
                
                                // fade in
                                if (leadNoteLength > fadePoint){
                                        fade = 1 - (leadNoteLength-fadePoint)/300;
                                // fade out
                                } else if (leadNoteLength<300){
                                        fade = leadNoteLength/300;
                                } else {
                                        fade = 1;
                                }
                
                                sample = 0;
                
                                // Generate oscillator
                                lead.generate();
                
                                // Get oscillator mix and multiply by .5 to reduce amplitude
                                // for a chord, we add the samples together.
                                sample = lead.getMix()*0.5*fade;

                                // Fill buffer for each channel
                                for (n=0; n<channelCount; n++){
                                        buffer[current + n] = sample;
                                } // for
                
                                leadNoteLength -= 1;
                        } // for 
                }
        
                // start the system
                var started = 0;
                function startSystem() {
                        if (started == 0) {
                                started = 1;
                                // Create an instance of the AudioDevice class
                                dev = audioLib.AudioDevice(audioCallback /* callback for the buffer fills */, 2 /* channelCount */);

                                sampleRate = dev.sampleRate;
        
                                // Create an instance of the Oscillator class... not sure what the 440 is all about - the first note?
                                lead = audioLib.Oscillator(sampleRate, 440);
                        } // if
                }
        </script>
</head>
<body>
  <div style="margin-bottom:20px">
    <span>Press the button to start the system.</span><br/>
    <input type="button" value="Start" onclick="javascript:startSystem();" />
  </div>
  <div style="margin-bottom:20px">
        <div><span>Press F5 to stop the system.</span></div>
  </div>
  <div style="margin-bottom:20px">
        <span>Output:</span>
        <ul style="margin-top:0">
                <li>Function Value: <span id="theValue">none</span></li>
                <li>Noised Value: <span id="theNoise">none</span></li>
				<li>MIDI Value: <span id="theMIDI">none</span></li>
                <li>Frequency: <span id="theFreq">none</span></li>
        </ul>
  </div>
  <div style="margin-bottom:20px">
    <span>Select a noise algorithm:</span><br/>
        <!--
        <input name="noiseSelect" type="radio" value="0" onclick="javascript:audioNoise.setNoiseGenerator(0);" />
        <span>Constant Noise</span><br/>
        -->
        <input checked="true" name="noiseSelect" type="radio" value="1" onclick="javascript:audioNoise.setNoiseGenerator(1);" />
        <span>White Noise</span><br/>
        <input name="noiseSelect"type="radio" value="2" onclick="javascript:audioNoise.setNoiseGenerator(2);" />
        <span>Perlin 1D</span><br/>
  </div>
  <div style="margin-bottom:20px">
    <span>Extended Debug Output:</span>
        <ul style="margin-top:0">
                <li>Debug Line 1: <span id="debugEcho1">none</span></li>
                <li>Debug Line 2: <span id="debugEcho2">none</span></li>
                <li>Debug Line 3: <span id="debugEcho3">none</span></li>
                <li>Debug Line 4: <span id="debugEcho4">none</span></li>
                <li>Debug Line 5: <span id="debugEcho5">none</span></li>
                <li>Debug Line 6: <span id="debugEcho6">none</span></li>
                <li>Debug Line 7: <span id="debugEcho7">none</span></li>
                <li>Debug Line 8: <span id="debugEcho8">none</span></li>
        </ul>
  </div>
  
</body>
</html>